package com.xinyue.game.client.network;

import com.xinyue.game.client.common.GameClientConfig;
import com.xinyue.network.codec.GameMessageContext;
import com.xinyue.network.codec.GameMessageHeader;
import com.xinyue.network.codec.GameMessageMeta;
import com.xinyue.network.codec.IGameMessage;
import com.xinyue.network.hamdlermapping.GameChannelContext;
import com.xinyue.network.hamdlermapping.GameHandlerMappingService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

/**
 * 定义Netty的客户端，用于连接服务器
 *
 * @author 王广帅
 * @date 2021年01月28日 12:12 上午
 */
public class GameClient {
    private static Logger logger = LoggerFactory.getLogger(GameClient.class);
    private String host;
    private int port;
    private Bootstrap bootstrap;
    private GameHandlerMappingService gameHandlerMappingService;
    private ApplicationContext applicationContext;
    private int maxTryTimes = 3;
    private int nowTryTimes = 0;
    private boolean isStop;
    private Channel channel;
    private GameClientConfig gameClientConfig;

    public GameClient(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        this.gameHandlerMappingService = applicationContext.getBean(GameHandlerMappingService.class);
        this.gameClientConfig = applicationContext.getBean(GameClientConfig.class);
    }

    public void startConnectServer(String host, int port) {
        this.host = host;
        this.port = port;
        this.nowTryTimes = 0;
        isStop = false;
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            bootstrap = new Bootstrap();
            bootstrap.group(workerGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {

                    //添加自定义编码，将对象转化为字节ByteBuf数据
                    ch.pipeline().addLast(new EncodeHandler());
                    //解决粘包和断包问题，从网络传输中读取完整的单个消息包
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024 * 8, 0, 4, -4, 0));
                    //添加自定义解码，将字节ByteBuf自动转化为对象
                    ch.pipeline().addLast(new DecodeHandler());
                    ch.pipeline().addLast(new ChannelHandShakeHandler());
                    //添加服务器返回的消息转发处理
                    ch.pipeline().addLast(new LogicHandler(gameHandlerMappingService));
                }
            });
            connectServer();
        } catch (Throwable e) {
            logger.error("服务器连接失败->{}:{}", host, port, e);
        }
    }

    private void connectServer() throws InterruptedException {
        logger.info("--->开始连接服务器：{}:{}", host, port);
        ChannelFuture f = bootstrap.connect(host, port);
        f.sync();
        logger.info("--->服务器连接成功");
        channel = f.channel();
        f.channel().closeFuture().addListener((future) -> {
            if (gameClientConfig.isOpenAutoConnect() && !isStop && nowTryTimes < maxTryTimes) {
                logger.info("--->连接断开，开始尝试重新连接->{}:{}...", host, port);
                connectServer();
                nowTryTimes++;
            }
        });
    }

    public void disconnect() {
        logger.info("正在与服务器断开连接...");
        if (channel == null || !channel.isActive() || !channel.isOpen()) {
            logger.info("与服务器断开连接成功");
            return;
        }
        if (bootstrap.config().group().isShutdown()) {
            logger.info("与服务器断开连接成功");
            return;
        }
        isStop = true;
        ChannelFuture f = channel.close();
        try {
            f.sync().await(5000);
            bootstrap.config().group().shutdownGracefully();
            logger.info("与服务器断开连接成功");
        } catch (Exception e) {
            logger.error("断开连接异常",e);
        }

    }

    public void sendMessage(IGameMessage gameMessage) {
        if (channel.isActive() && channel.isOpen()) {
            channel.writeAndFlush(gameMessage);
        } else {
            logger.error("--->未与服务器成功建立连接，不能发送消息!!!");
        }
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}
